#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/compiler.h>


#define CTRL_LED_CMD    (0x01)


static int drv_probe(struct platform_device *);
static int drv_remove(struct platform_device *);
static void drv_shutdown(struct platform_device *);
static int drv_suspend(struct platform_device *, pm_message_t state);
static int drv_suspend_late(struct platform_device *, pm_message_t state);
static int drv_resume_early(struct platform_device *);
static int drv_resume(struct platform_device *);


static int dev_open(struct inode *, struct file *);
static ssize_t dev_read(struct file *, char __user *, size_t , loff_t *);
static ssize_t dev_write(struct file *, const char __user *, size_t , loff_t *);
static int dev_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
static int dev_release(struct inode *, struct file *);



struct led_dev
{
    char                    name[20];      //设备名
    unsigned int            minor;      //次设备号
    unsigned int            pin_grp;    //pin脚所在的组，如GPF4,GPF5,GPF6
    unsigned int            pin_dat;    //pin脚的数据寄存器由哪个bit控制
    struct list_head        node;       //存放在g_pdrv.head链表中的节点
    struct class_device 	*cls_dev;
    
};

struct plat_drv
{
    struct platform_driver      pdrv;
    struct list_head            head;
    int                         major;
    int                         minor;
    char                        *name;
    volatile unsigned long       *con_reg;   //控制寄存器
    volatile unsigned long       *dat_reg;   //数据寄存器
    struct class 			    *cls;
    struct file_operations      fops;
    int                         flags;
};

static struct plat_drv  g_pdrv = {
    .pdrv = {
    	.probe  = drv_probe,
	    .remove = drv_remove,
	    .shutdown   = drv_shutdown,
	    .suspend    = drv_suspend,
	    .suspend_late   = drv_suspend_late,
	    .resume_early   = drv_resume_early,
	    .resume = drv_resume,
	    .driver = {
	        .name   = "platform_leds",
	        .bus    = &platform_bus_type,
	        .owner  = THIS_MODULE,
	    	.probe  = NULL,
	        .remove = NULL,
	        .shutdown   = NULL,
	        .suspend    = NULL,
	        .resume     = NULL,
	    },
    },
    .head   = LIST_HEAD_INIT(g_pdrv.head),
    .major  = -1,
    .minor  = 0,
    .name   = "leds",
    .con_reg = NULL,
    .dat_reg = NULL,
    .fops   = {
        .open = dev_open,
        .read = dev_read,
        .write = dev_write,
        .ioctl = dev_ioctl,
        .release = dev_release,
    },
    .flags  = -1,
};



static int drv_probe(struct platform_device *pdev)
{
    struct resource *addr_res;
    struct resource *pin_res;
    struct led_dev *ldev;

    printk(KERN_INFO "LED Probing...\n");
    //[0]. 注册一个字设备,并创建一个led类
    if(g_pdrv.major == -1)
    {
        g_pdrv.major = register_chrdev(0, g_pdrv.name, &g_pdrv.fops);
        if(g_pdrv.major < 0)
	    {
		    printk(KERN_ERR "Register char device[%s] failed!\n", g_pdrv.name);
		    return -EINVAL;
	    }
        g_pdrv.cls = class_create(THIS_MODULE, "led");
	    if(IS_ERR(g_pdrv.cls))
	    {
		    printk(KERN_ERR "Create class [%s] failed", "led");
		    unregister_chrdev(g_pdrv.major, g_pdrv.name);
            g_pdrv.major = -1;
            return -EINVAL;
	    }
    } 
    //[1]. 获取LED相关的GPIO地址、pin脚资源等

    addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if(!addr_res)
    {
        printk(KERN_ERR "[LED driver probe]: Get addr resource failed!\n");
        return -EINVAL;
    }
    pin_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
        if(!pin_res)
    {
        printk(KERN_ERR "[LED driver probe]: Get pin resource failed!\n");
        return -EINVAL;
    }     
    //[2]. 将LED物理地址映射到内核虚拟地址
    if(!g_pdrv.con_reg)
    {
        g_pdrv.con_reg = (volatile unsigned long *)ioremap(addr_res->start, addr_res->end - addr_res->start + 4);
        if(!g_pdrv.con_reg)
        {
            printk(KERN_ERR "Map the leds's physical address to virtual address failed.\n");
            return -EINVAL;
        }
        g_pdrv.dat_reg = g_pdrv.con_reg + 1;
        printk(KERN_INFO "Phy address: 0x%x, Virt ctrl reg address: 0x%x, dat reg address: 0x%x\n", addr_res->start,  
        g_pdrv.con_reg, g_pdrv.dat_reg);
    }

    //[3]. 分配led_dev内存并将其放到g_pdrv链表中
    ldev = kmalloc(sizeof (struct led_dev), GFP_KERNEL);
    if(!ldev)
    {
        printk(KERN_WARNING "[LED driver probe]: Malloc ram memory failed!");
        return -EINVAL;
    }
    ldev->pin_grp = pin_res->start;
    ldev->pin_dat = pin_res->end;
    ldev->minor   = g_pdrv.minor++;
    INIT_LIST_HEAD(&ldev->node);
    sprintf(ldev->name, "led%u", ldev->minor);
    ldev->cls_dev = class_device_create(g_pdrv.cls, NULL, MKDEV(g_pdrv.major, ldev->minor), NULL, ldev->name);
	if(unlikely(IS_ERR(ldev->cls_dev)))
	{
        printk(KERN_ERR "Create class device [%s] failed", ldev->name);
        kfree(ldev);
        ldev = NULL;
        return -EINVAL;
    }    
    list_add(&g_pdrv.head, &ldev->node);
    return 0;
}
static int drv_remove(struct platform_device *pdev)
{
    return 0;
}
static void drv_shutdown(struct platform_device *pdev)
{
    return;
}
static int drv_suspend(struct platform_device *pdev, pm_message_t state)
{
    return 0;
}
static int drv_suspend_late(struct platform_device *pdev, pm_message_t state)
{
    return 0;
}
static int drv_resume_early(struct platform_device *pdev)
{
    return 0;
}
static int drv_resume(struct platform_device *pdev)
{
    return 0;
}


/*
 * @func: device open
 */
static int dev_open(struct inode *node, struct file *filep)
{
    int isFind = 0;
    struct led_dev *ldev;
    int dev_minor = MINOR(node->i_rdev);
    printk(KERN_INFO "open led[%d] device\n", dev_minor);
    
    list_for_each_entry(ldev, &g_pdrv.head, node) 
    {
        if(ldev->minor == dev_minor)
        {
            isFind = 1;
            break;
        }
    }
    if(!isFind)
        return -EINVAL;
    printk(KERN_INFO "ctrl reg addr: 0x%x, data reg addr: 0x%xpin_grp = %d, pin_dat = %d\n", g_pdrv.con_reg, 
    g_pdrv.dat_reg,ldev->pin_grp, ldev->pin_dat);
    *g_pdrv.con_reg &= ~(3 << (2 * ldev->pin_grp));
    *g_pdrv.con_reg |= (1 << (2 * ldev->pin_grp));
    *g_pdrv.dat_reg |= (1 << ldev->pin_dat);
	return 0;
}

/*
 * @func: device read
 */
static ssize_t dev_read(struct file *filep, char __user *buffer, size_t size, loff_t *offset)
{
	printk(KERN_INFO "read device\n");
	return 0;
}

/*
 * @func: device write
 */
static ssize_t dev_write(struct file *filep, const char __user *buffer, size_t size, loff_t *offset)
{
	printk(KERN_INFO "write device\n");
	return 0;
}

/*
 * device ioctl
 */
static int dev_ioctl(struct inode *node, struct file *filep, unsigned int cmd, unsigned long arg)
{
    struct led_dev *ldev;
    unsigned long *onoff;
    int dev_minor = MINOR(node->i_rdev);
    printk(KERN_INFO "ioctl led%d device\n", dev_minor);
    list_for_each_entry(ldev, &g_pdrv.head, node) 
    {
        if(ldev->minor == dev_minor)
            break;
    }
    switch(cmd)
    {
        case CTRL_LED_CMD:
        {
            printk(KERN_INFO "ioctl led cmd, pin_dat = %d\n", ldev->pin_dat);
            onoff =(unsigned long *)arg;
            if(*onoff == 0)
                *g_pdrv.dat_reg &= ~(1 << ldev->pin_dat);
            else
                *g_pdrv.dat_reg |= (1 << ldev->pin_dat);
            break;
        }
        default:
            break;
    }

    return 0;
}


/*
 * @func: device close
 */

static int dev_release(struct inode *node, struct file *filep)
{
	printk(KERN_INFO "release led[%d]\n", MINOR(node->i_rdev));
	return 0;
}


static int __init leddrv_init(void)
{
    int ret;
    printk(KERN_INFO "The driver 'leddrv_init' is inserted!\n");
    //[0]. 注册LED平台驱动
    ret = platform_driver_register(&g_pdrv.pdrv);
    if(ret != 0)
    {
        printk(KERN_ERR "Register platform led driver failed!\n");
        return -EINVAL;
    }
    g_pdrv.flags = 0;
    return 0;
}
static void __exit leddrv_exit(void)
{
    struct led_dev *ldev;
    printk(KERN_INFO "The driver 'leddrv_exit' is removed!\n");
    if(!g_pdrv.flags)
    {
        if(g_pdrv.major > 0)
            unregister_chrdev(g_pdrv.major, g_pdrv.name);
        list_for_each_entry(ldev, &g_pdrv.head, node) 
        {
            if(ldev->cls_dev)
                class_device_unregister(ldev->cls_dev);
            kfree(ldev);
        }
        if(g_pdrv.cls)
            class_destroy(g_pdrv.cls);
        platform_driver_unregister(&g_pdrv.pdrv);
        if(g_pdrv.con_reg)
            iounmap(g_pdrv.con_reg);
    }
}



module_init(leddrv_init);
module_exit(leddrv_exit);


MODULE_LICENSE("GPL");

